Skip to content

feat(guard): runtime DNS SSRF bypass detection — WRD-RES-EXFIL-DNS-SSRF (#11)#73

Merged
ernestprovo23 merged 2 commits into
mainfrom
feat/dse-58-dns-ssrf-resolution
Jun 29, 2026
Merged

feat(guard): runtime DNS SSRF bypass detection — WRD-RES-EXFIL-DNS-SSRF (#11)#73
ernestprovo23 merged 2 commits into
mainfrom
feat/dse-58-dns-ssrf-resolution

Conversation

@ernestprovo23

Copy link
Copy Markdown
Member

Summary

  • Closes DSE-58 (Linear) / resolves GH issue runtime DNS resolution for exfil / SSRF matching #11
  • Adds WRD-RES-EXFIL-DNS-SSRF: the guard now DNS-resolves URL hostnames from tools/call results at runtime and error-replaces when any resolved IP falls in SSRF_NETWORKS
  • Closes the bypass where WRD-RES-EXFIL-IP-LITERAL missed destinations whose hostname (e.g. 169.254.169.254.nip.io) resolves to a private/metadata IP rather than appearing as a raw literal

How it works

  1. After inspect_result() (pure, no IO), the guard extracts non-IP-literal URL hostnames from each result block via res_dns.extract_dns_candidates()
  2. res_dns.resolve_ssrf_hits() resolves them in a ThreadPoolExecutor bounded by 1 second across all hosts — fail-open (any OSError → no hit)
  3. Resolved IPs checked against SSRF_NETWORKS (same table as the raw-IP-literal rule)
  4. Findings stamped WRD-RES-EXFIL-DNS-SSRF, block tier, error-replace (same posture as WRD-RES-EXFIL-DOMAIN)

Opt-out

mcp-warden guard --no-block-exfil-dns-ssrf node ./server.js
mcp-warden guard --no-block-deterministic node ./server.js

Files changed

File Change
src/mcp_warden/res_dns.py New — DNS resolution module
src/mcp_warden/res_catalog.py +inspect_exfil_dns_ssrf()
src/mcp_warden/result_inspection.py Add rule to BLOCK_RULES
src/mcp_warden/guard_loop.py GuardConfig.no_block_exfil_dns_ssrf + _DET_OPTOUT entry
src/mcp_warden/guard_result.py DNS check after inspect_result; _dns_ssrf_findings(); add to error_rules
src/mcp_warden/cli_guard.py --no-block-exfil-dns-ssrf flag wired
tests/test_res_dns.py New — 19 tests (candidates + resolution, mocked DNS)
tests/test_guard_posture.py +4 posture tests for new rule
CHANGELOG.md [Unreleased] entry; remove DNS from out-of-scope list

Test plan

  • uv run pytest tests/test_res_dns.py → 19/19 pass
  • uv run pytest tests/test_guard_posture.py → 14/14 pass
  • Full suite: 530 passed, 1 skipped (up from 512 collected baseline)
  • Fail-open: OSError from DNS → no findings, frame forwarded
  • Raw IP literals skipped (handled by existing WRD-RES-EXFIL-IP-LITERAL)
  • --no-block-exfil-dns-ssrf and --no-block-deterministic both demote the rule

🤖 Generated with Claude Code

https://claude.ai/code/session_01Btjz6CMgR5U9EZmzgCsaG5

…FIL-DNS-SSRF, #11)

Closes the bypass where WRD-RES-EXFIL-IP-LITERAL could not fire because the
tools/call result contained a DNS hostname (e.g. 169.254.169.254.nip.io) rather
than a raw IP literal.

New rule WRD-RES-EXFIL-DNS-SSRF: guard resolves URL hostnames from result text at
runtime via concurrent.futures.ThreadPoolExecutor (bounded 1 s, fail-open) and
error-replaces when any resolved IP falls in SSRF_NETWORKS (link-local, loopback,
RFC1918, IPv6 ULA/link-local/loopback). Opt-out via --no-block-exfil-dns-ssrf or
--no-block-deterministic. Offline inspect command unchanged.

Files: res_dns.py (new), res_catalog.py, result_inspection.py, guard_loop.py,
guard_result.py, cli_guard.py, tests/test_res_dns.py (19 new), test_guard_posture.py
(+4). All 530 tests pass.

Closes DSE-58.
…workflow

The repo transferred to DataScience-EngineeringExperts but the
--certificate-identity flag still referenced the personal account
(ernestprovo23). Fulcio mints a cert whose SAN reflects the actual
org, so the exact-string equality check in sigstore 4.3.0 failed.
Updated both the verify and negative-proof steps.

Fixes Sigstore sign+verify (live) failure on PR #73.
@ernestprovo23 ernestprovo23 merged commit 94e4a74 into main Jun 29, 2026
9 checks passed
@ernestprovo23 ernestprovo23 deleted the feat/dse-58-dns-ssrf-resolution branch June 29, 2026 02:45
ernestprovo23 added a commit that referenced this pull request Jun 29, 2026
…-57) (#74)

* feat(guard): runtime DNS resolution SSRF bypass detection (WRD-RES-EXFIL-DNS-SSRF, #11)

Closes the bypass where WRD-RES-EXFIL-IP-LITERAL could not fire because the
tools/call result contained a DNS hostname (e.g. 169.254.169.254.nip.io) rather
than a raw IP literal.

New rule WRD-RES-EXFIL-DNS-SSRF: guard resolves URL hostnames from result text at
runtime via concurrent.futures.ThreadPoolExecutor (bounded 1 s, fail-open) and
error-replaces when any resolved IP falls in SSRF_NETWORKS (link-local, loopback,
RFC1918, IPv6 ULA/link-local/loopback). Opt-out via --no-block-exfil-dns-ssrf or
--no-block-deterministic. Offline inspect command unchanged.

Files: res_dns.py (new), res_catalog.py, result_inspection.py, guard_loop.py,
guard_result.py, cli_guard.py, tests/test_res_dns.py (19 new), test_guard_posture.py
(+4). All 530 tests pass.

Closes DSE-58.

* fix(ci): correct Sigstore certificate-identity org in integrity-gate workflow

The repo transferred to DataScience-EngineeringExperts but the
--certificate-identity flag still referenced the personal account
(ernestprovo23). Fulcio mints a cert whose SAN reflects the actual
org, so the exact-string equality check in sigstore 4.3.0 failed.
Updated both the verify and negative-proof steps.

Fixes Sigstore sign+verify (live) failure on PR #73.

* feat(transport): add HTTP/SSE transport support to pin and check (DSE-57)

Add streamable_http_client path to capture.py (_capture_http_async,
capture_surface_http, capture_surface_http_sync). Extend CapturedSurface
and ServerIdentity with optional url field (backward-compat, defaults None).
Update _server_identity() to hash the URL when set. Route check_core
run_check_full() and cli pin/check commands through HTTP capture when
--url is provided; mutually exclusive with positional server-cmd.
6 new tests (async + sync, mocked transport, error paths).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant